package com.resrun.utils;

import com.resrun.dto.CertGenDto;
import com.resrun.dto.CertValidDateDto;
import com.resrun.dto.GenerateCertDto;
import com.resrun.exception.CaException;
import com.resrun.model.CARequest;
import com.resrun.service.pojo.BaseCertificateInfo;
import com.resrun.service.pojo.CertificateInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * CA证书工具类
 *
 * @author hupan
 * @since 2024-01-26
 */
@Slf4j
public final class CaUtil {

    public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    private CaUtil() {
        throw new UnsupportedOperationException("工具栏不支持格式化");
    }


    public static String generateSubject(String countryName, String provinceName, String cityName, String orgnizationName, String orgnizationUnitName, String commonName) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(countryName)) {
            sb.append("C=").append(countryName).append(",");
        }

        if (StringUtils.isNotBlank(provinceName)) {
            sb.append("ST=").append(provinceName).append(",");
        }

        if (StringUtils.isNotBlank(cityName)) {
            sb.append("L=").append(cityName).append(",");
        }

        if (StringUtils.isNotBlank(orgnizationName)) {
            sb.append("O=").append(orgnizationName).append(",");
        }

        if (StringUtils.isNotBlank(orgnizationUnitName)) {
            sb.append("OU=").append(orgnizationUnitName).append(",");
        }

        if (StringUtils.isNotBlank(commonName)) {
            sb.append("CN=").append(commonName).append(",");
        }

        if (sb.isEmpty()) {
            throw new CaException("生成Subject失败");
        }

        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    public static String generateSubject(CARequest caRequest) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(caRequest.getCountry())) {
            sb.append("C=").append(caRequest.getCountry()).append(",");
        }

        if (StringUtils.isNotBlank(caRequest.getProvince())) {
            sb.append("ST=").append(caRequest.getProvince()).append(",");
        }

        if (StringUtils.isNotBlank(caRequest.getLocality())) {
            sb.append("L=").append(caRequest.getLocality()).append(",");
        }

        if (StringUtils.isNotBlank(caRequest.getOrganization())) {
            sb.append("O=").append(caRequest.getOrganization()).append(",");
        }

        if (StringUtils.isNotBlank(caRequest.getOrganizationUnit())) {
            sb.append("OU=").append(caRequest.getOrganizationUnit()).append(",");
        }

        if (StringUtils.isNotBlank(caRequest.getCommonName())) {
            sb.append("CN=").append(caRequest.getCommonName()).append(",");
        }

        if (sb.isEmpty()) {
            throw new CaException("生成Subject失败");
        }

        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    /**
     * 生成Subject信息
     *
     * @param countryName         Country Name (国家/地区)，eg: CN
     * @param provinceName        State or Province Name (州/省/市/自治区)，eg: Beijing
     * @param cityName            Locality Name (城市/地点)，eg: Beijing
     * @param orgnizationName     Organization Name (组织，可以是公司名称)，eg: 北京创新乐知网络技术有限公司
     * @param orgnizationUnitName Organizational Unit Name (组织单位，可以是单位部门名称)
     * @param commonName          Common Name (通用名称，可以是服务器ip或者域名)，eg: 192.168.30.71 or www.baidu.com
     * @return X500Name Subject
     */
    public static X500Name generateX500Name(String countryName, String provinceName, String cityName, String orgnizationName, String orgnizationUnitName, String commonName) {
        X500NameBuilder x500NameBuilder = new X500NameBuilder();

        if (StringUtils.isNotBlank(countryName)) {
            x500NameBuilder.addRDN(BCStyle.C, countryName);
        }

        if (StringUtils.isNotBlank(provinceName)) {
            x500NameBuilder.addRDN(BCStyle.ST, provinceName);
        }

        if (StringUtils.isNotBlank(cityName)) {
            x500NameBuilder.addRDN(BCStyle.L, cityName);
        }

        if (StringUtils.isNotBlank(orgnizationName)) {
            x500NameBuilder.addRDN(BCStyle.O, orgnizationName);
        }

        if (StringUtils.isNotBlank(orgnizationUnitName)) {
            x500NameBuilder.addRDN(BCStyle.OU, orgnizationUnitName);
        }

        if (StringUtils.isNotBlank(commonName)) {
            x500NameBuilder.addRDN(BCStyle.CN, commonName);
        }

        return x500NameBuilder.build();
    }

    /**
     * 生成Subject信息
     *
     * @return CARequest caRequest
     */
    public static X500Name generateX500Name(CARequest caRequest) {
        if (caRequest == null) {
            throw new CaException("参数不正确");
        }

        X500NameBuilder x500NameBuilder = new X500NameBuilder();

        if (StringUtils.isNotBlank(caRequest.getCountry())) {
            x500NameBuilder.addRDN(BCStyle.C, caRequest.getCountry());
        }

        if (StringUtils.isNotBlank(caRequest.getProvince())) {
            x500NameBuilder.addRDN(BCStyle.ST, caRequest.getProvince());
        }

        if (StringUtils.isNotBlank(caRequest.getLocality())) {
            x500NameBuilder.addRDN(BCStyle.L, caRequest.getLocality());
        }

        if (StringUtils.isNotBlank(caRequest.getOrganization())) {
            x500NameBuilder.addRDN(BCStyle.O, caRequest.getOrganization());
        }

        if (StringUtils.isNotBlank(caRequest.getOrganizationUnit())) {
            x500NameBuilder.addRDN(BCStyle.OU, caRequest.getOrganizationUnit());
        }

        if (StringUtils.isNotBlank(caRequest.getCommonName())) {
            x500NameBuilder.addRDN(BCStyle.CN, caRequest.getCommonName());
        }

        return x500NameBuilder.build();
    }

    public static X509Certificate generateCACertificate(final CertGenDto param) throws OperatorCreationException, CertificateException, CertIOException {
        Security.addProvider(new BouncyCastleProvider());

        // 构建证书请求
        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(param.getX500Name(), param.getKeyPair().getPublic());

        // 用CA私钥签署证书请求
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM);
        ContentSigner signer = signerBuilder.build(param.getKeyPair().getPrivate());
        PKCS10CertificationRequest request = requestBuilder.build(signer);

        // 构建证书
        CertValidDateDto validDate = getValidDate(param.getDuration(), TimeUnit.SECONDS);
        X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(param.getX500Name(), BigInteger.valueOf(System.currentTimeMillis()), validDate.getEffectiveDate(), validDate.getExpirationDate(), request.getSubject(), param.getKeyPair().getPublic());

        BasicConstraints basicConstraints = new BasicConstraints(1);
        KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment | KeyUsage.keyCertSign);
        certificateBuilder.addExtension(Extension.basicConstraints, true, basicConstraints.toASN1Primitive());
        certificateBuilder.addExtension(Extension.keyUsage, true, keyUsage.toASN1Primitive());

        // 使用CA私钥签署证书
        X509CertificateHolder certificateHolder = certificateBuilder.build(signer);
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter();

        return converter.setProvider(new BouncyCastleProvider()).getCertificate(certificateHolder);
    }

    public static X509Certificate generateCACertificate(final KeyPair keyPair, X500Name x500Name) throws OperatorCreationException, CertificateException, CertIOException {
        Security.addProvider(new BouncyCastleProvider());

        // 构建证书请求
        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(x500Name, keyPair.getPublic());

        // 用CA私钥签署证书请求
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM);
        ContentSigner signer = signerBuilder.build(keyPair.getPrivate());
        PKCS10CertificationRequest request = requestBuilder.build(signer);

        // 构建证书
        X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(x500Name, BigInteger.valueOf(System.currentTimeMillis()), new Date(System.currentTimeMillis() - 10000), new Date(System.currentTimeMillis() + 86400000L * 365), request.getSubject(), keyPair.getPublic());

        BasicConstraints basicConstraints = new BasicConstraints(1);
        KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment | KeyUsage.keyCertSign);
        certificateBuilder.addExtension(Extension.basicConstraints, true, basicConstraints.toASN1Primitive());
        certificateBuilder.addExtension(Extension.keyUsage, true, keyUsage.toASN1Primitive());

        // 使用CA私钥签署证书
        X509CertificateHolder certificateHolder = certificateBuilder.build(signer);
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter();

        return converter.setProvider(new BouncyCastleProvider()).getCertificate(certificateHolder);
    }

    public static X509Certificate generateCACertificate(final PrivateKey caPrivateKey, final PublicKey caPublicKey, X500Name x500Name) throws OperatorCreationException, CertificateException, CertIOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // 构建证书请求
        PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(x500Name, caPublicKey);

        // 用CA私钥签署证书请求
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM);
        ContentSigner signer = signerBuilder.build(caPrivateKey);
        PKCS10CertificationRequest request = requestBuilder.build(signer);

        // 构建证书
        CertValidDateDto defaultValidDate = getDefaultValidDate();
        X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(x500Name, BigInteger.valueOf(System.currentTimeMillis()), defaultValidDate.getEffectiveDate(), defaultValidDate.getExpirationDate(), request.getSubject(), caPublicKey);

        BasicConstraints basicConstraints = new BasicConstraints(1);
        KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyEncipherment | KeyUsage.keyCertSign);
        certificateBuilder.addExtension(Extension.basicConstraints, true, basicConstraints.toASN1Primitive());
        certificateBuilder.addExtension(Extension.keyUsage, true, keyUsage.toASN1Primitive());

        // 使用CA私钥签署证书
        X509CertificateHolder certificateHolder = certificateBuilder.build(signer);
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter();

        return converter.setProvider(new BouncyCastleProvider()).getCertificate(certificateHolder);
    }

    private static CertValidDateDto getValidDate(long duration, TimeUnit timeUnit) {
        long currTimestamp = System.currentTimeMillis();
        Date effectiveDate = new Date(currTimestamp);
        Date expirationDate = new Date(currTimestamp + timeUnit.toMillis(duration));

        return CertValidDateDto.of(effectiveDate, expirationDate);
    }

    private static CertValidDateDto getDefaultValidDate() {
        long currTimestamp = System.currentTimeMillis();
        Date effectiveDate = new Date(currTimestamp);
        Date expirationDate = new Date(currTimestamp + (long) 365 * 24 * 60 * 60 * 1000);

        return CertValidDateDto.of(effectiveDate, expirationDate);
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * 证书类型
     *
     * @param generateCertDto 证书生成参数
     * @return CertificateInfo
     */
    public static CertificateInfo generateCertificate(BaseCertificateInfo root, GenerateCertDto generateCertDto) throws GeneralSecurityException, IOException, OperatorCreationException {
        X500Name issuer = new X500Name(generateCertDto.getSubject());
        PrivateKey signPrivateKey = null;
        List<Certificate> chains = new ArrayList<>(5);
        if (root != null) {
            KeyStore.PrivateKeyEntry rootPK = (KeyStore.PrivateKeyEntry) root.getCert().getEntry(root.getAlias(), new KeyStore.PasswordProtection(root.getPassword().toCharArray()));
            X509Certificate rootCert = (X509Certificate) rootPK.getCertificate();
            issuer = X500Name.getInstance(rootCert.getSubjectX500Principal().getEncoded());
            signPrivateKey = rootPK.getPrivateKey();
            Certificate[] rootChains = rootPK.getCertificateChain();
            Collections.addAll(chains, rootChains);
        }

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, null);

        Date startDate = new Date(System.currentTimeMillis());
        Date expireDate = new Date(System.currentTimeMillis() + (86400000L * 365 * generateCertDto.getLifespan()));

        String serial = UUID.randomUUID().toString().replace("-", "");
        BigInteger serialBig = str2BigInteger(serial);

        if (signPrivateKey == null) {
            signPrivateKey = keyPair.getPrivate();
        }

        Certificate cert = generateV3(issuer, new X500Name(generateCertDto.getSubject()), serialBig, startDate, expireDate, keyPair.getPublic(), signPrivateKey, null);

        //用户证书要放在证书链的最前面
        chains.add(0, cert);

        keyStore.setKeyEntry(generateCertDto.getAlias(), keyPair.getPrivate(), generateCertDto.getPassowrd().toCharArray(), chains.toArray(new Certificate[0]));
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        keyStore.store(outputStream, generateCertDto.getPassowrd().toCharArray());

        CertificateInfo certificateInfo = new CertificateInfo();
        certificateInfo.setAlias(generateCertDto.getAlias());
        certificateInfo.setAlgorithm("RSA");
        certificateInfo.setPrivateKey(signPrivateKey.getEncoded());
        certificateInfo.setPublicKey(keyPair.getPublic().getEncoded());
        certificateInfo.setAlgorithmSignature(SIGNATURE_ALGORITHM);
        certificateInfo.setPassword(generateCertDto.getPassowrd());
        certificateInfo.setCertFileType("jks");
        certificateInfo.setSerial(serial);
        certificateInfo.setJks(outputStream.toByteArray());
        certificateInfo.setPfx(coverToPfx(outputStream.toByteArray(), generateCertDto.getPassowrd()));
        certificateInfo.setTermOfValidityStartTime(startDate);
        certificateInfo.setTermOfValidityEndTime(expireDate);

        return certificateInfo;
    }

    public static Certificate generateV3(X500Name issuer, X500Name subject, BigInteger serial, Date startDate, Date expireDate, PublicKey publicKey, PrivateKey privKey, List<Extension> extensions) throws IOException, CertificateException, OperatorCreationException {
        X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, serial, startDate, expireDate, subject, publicKey);
        ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(privKey);
        //privKey是CA的私钥，publicKey是待签名的公钥，那么生成的证书就是被CA签名的证书。
        if (extensions != null) {
            for (Extension ext : extensions) {
                builder.addExtension(ext.getExtnId(), ext.isCritical(), ext.getExtnValue());
            }
        }

        X509CertificateHolder holder = builder.build(sigGen);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream is1 = new ByteArrayInputStream(holder.toASN1Structure().getEncoded());
        X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(is1);
        is1.close();

        return x509Certificate;
    }


//    public static void main(String[] args) throws Exception {
////
//        KeyStore store = loadJKS("Ting111");
//        BaseCertificateInfo baseCertificateInfo = new BaseCertificateInfo();
//        baseCertificateInfo.setAlias("root");
//        baseCertificateInfo.setCert(store);
//        baseCertificateInfo.setPassword("123456");
//        String x500Name = "CN=Ting222,OU=研发部,O=资源律动,L=BeiJing,ST=BeiJing,C=CN";
//
//        CertificateInfo certificateInfo = GenerateRootCertificate.instance(x500Name, CertificateType.RSA).generateCertificate(baseCertificateInfo,"123456",10);
//        FileUtils.writeByteArrayToFile(new File("C:\\Users\\Administrator\\Desktop\\tem\\cert\\Ting222.jks"), certificateInfo.getJks());
//
//    }

    private static BigInteger str2BigInteger(String str) {
        StringBuilder sb = new StringBuilder();
        //将字符串转换为字符数组
        char[] ch = str.toCharArray();
        for (char c : ch) {
            String hexString = Integer.toHexString(c);
            sb.append(hexString);
        }

        return new BigInteger(sb.toString());
    }

    private static String big2String(String str) {
        StringBuilder result = new StringBuilder();
        char[] charArray = str.toCharArray();
        for (int i = 0; i < charArray.length; i = i + 2) {
            String st = "" + charArray[i] + charArray[i + 1];
            char ch1 = (char) Integer.parseInt(st, 16);
            result.append(ch1);
        }
        return result.toString();
    }

    public static byte[] coverToPfx(byte[] jks, String password) {
        try {
            KeyStore inputKeyStore = KeyStore.getInstance("JKS");
            ByteArrayInputStream inputStream = new ByteArrayInputStream(jks);
            inputKeyStore.load(inputStream, password.toCharArray());
            KeyStore outputKeyStore = KeyStore.getInstance("PKCS12");
            outputKeyStore.load(null, password.toCharArray());
            Enumeration<String> enums = inputKeyStore.aliases();

            while (enums.hasMoreElements()) { // we are readin just one certificate.
                String keyAlias = enums.nextElement();
                if (inputKeyStore.isKeyEntry(keyAlias)) {
                    Key key = inputKeyStore.getKey(keyAlias, password.toCharArray());
                    Certificate[] certChain = inputKeyStore.getCertificateChain(keyAlias);
                    outputKeyStore.setKeyEntry(keyAlias, key, password.toCharArray(), certChain);
                }
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            outputKeyStore.store(out, password.toCharArray());
            out.close();
            return out.toByteArray();
        } catch (Exception e) {
            log.error("PFX转换异常", e);
            return new byte[0];
        }
    }

    public static KeyStore loadJKS(String name) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
        KeyStore store = KeyStore.getInstance("JKS");
        File file = new File(name + ".jks");
        store.load(Files.newInputStream(file.toPath()), "123456".toCharArray());

        return store;
    }

}
